﻿#include "frameless_widget_p.h"

// Qt's
#include <QLabel>
#include <QVBoxLayout>
#include <QPaintEvent>
#include <QPushButton>
#include <QPainter>
#include <QPainterPath>
#include <QApplication>

// Custom's
#include "frameless_widget_p.h"

constexpr uint8_t ShadowWidth = 6;
constexpr uint8_t TitleHeight = 32;
constexpr uint8_t DragableWidth = 6;
constexpr uint8_t BorderWidth = 4;

GAntFramelessWidgetPrivate::GAntFramelessWidgetPrivate(GAntFramelessWidget* qptr)
    : QObject(qptr)
    , q_ptr(qptr)
    , m_ePos{ MousePos::No }
    , m_bIsDrag{ false }
    , m_bIsPressTitle{ false }
    , m_bIsMoveable{ true }
    , m_bIsFixedHeight{ false }
    , m_bIsFixedWidth{ false }
{
}

void GAntFramelessWidgetPrivate::updateRects(const QRect& rect)
{
    m_rectLeftBorder.setTopLeft(rect.topLeft());
    m_rectLeftBorder.setSize({ ShadowWidth, rect.height() });

    m_rectRightBorder.setTopLeft(rect.topRight() - QPoint{ ShadowWidth, 0 });
    m_rectRightBorder.setSize({ ShadowWidth, rect.height() });

    m_rectTopBorder.setTopLeft(rect.topLeft() + QPoint{ 0, ShadowWidth });
    m_rectTopBorder.setSize({ rect.width(),  DragableWidth });

    m_rectBottomBorder.setTopLeft(rect.bottomLeft() - QPoint{ 0, ShadowWidth });
    m_rectBottomBorder.setSize({ rect.width(),  ShadowWidth });

    m_rectTitle.setTopLeft(rect.topLeft() + QPoint{ 0, ShadowWidth + DragableWidth });
    m_rectTitle.setSize({ rect.width(),  TitleHeight - DragableWidth });
}

void GAntFramelessWidgetPrivate::updateMouseArrow()
{
    Q_Q(GAntFramelessWidget);

    switch (m_ePos)
    {
    case GAntFramelessWidgetPrivate::TopLeft:
    case GAntFramelessWidgetPrivate::BottomRight:
        q->setCursor(Qt::SizeFDiagCursor);
        break;
    case GAntFramelessWidgetPrivate::Top:
    case GAntFramelessWidgetPrivate::Bottom:
        q->setCursor(Qt::SizeVerCursor);
        break;
    case GAntFramelessWidgetPrivate::TopRight:
    case GAntFramelessWidgetPrivate::BottomLeft:
        q->setCursor(Qt::SizeBDiagCursor);
        break;
    case GAntFramelessWidgetPrivate::Right:
    case GAntFramelessWidgetPrivate::Left:
        q->setCursor(Qt::SizeHorCursor);
        break;
    case GAntFramelessWidgetPrivate::No:
        q->setCursor(Qt::ArrowCursor);
        break;
    default:
        break;
    }
}

void GAntFramelessWidgetPrivate::resizeWidget(const QPoint& pos)
{
    Q_Q(GAntFramelessWidget);

    auto origRect = q->frameGeometry();

    int left = origRect.left();
    int top = origRect.top();
    int right = origRect.right();
    int bottom = origRect.bottom();

    origRect.getCoords(&left, &top, &right, &bottom);

    switch (m_ePos)
    {
    case GAntFramelessWidgetPrivate::TopLeft:
        left = pos.x();
        top = pos.y();
        break;
    case GAntFramelessWidgetPrivate::Top:
        top = pos.y();
        break;
    case GAntFramelessWidgetPrivate::TopRight:
        right = pos.x();
        top = pos.y();
        break;
    case GAntFramelessWidgetPrivate::Right:
        right = pos.x();
        break;
    case GAntFramelessWidgetPrivate::BottomRight:
        right = pos.x();
        bottom = pos.y();
        break;
    case GAntFramelessWidgetPrivate::Bottom:
        bottom = pos.y();
        break;
    case GAntFramelessWidgetPrivate::BottomLeft:
        left = pos.x();
        bottom = pos.y();
        break;
    case GAntFramelessWidgetPrivate::Left:
        left = pos.x();
        break;
    default:
        break;
    }

    QRect newRect{ QPoint{left, top}, QPoint{right, bottom} };

    if (newRect.width() < q->minimumWidth() || newRect.width() > q->maximumWidth()) {
        if (newRect.left() != origRect.left()) {
            newRect.setLeft(origRect.left());
        } else {
            newRect.setRight(origRect.right());
        }
    }

    if (newRect.height() < q->minimumHeight() || newRect.height() > q->maximumHeight()) {
        if (newRect.top() != origRect.top()) {
            newRect.setTop(origRect.top());
        } else {
            newRect.setBottom(origRect.bottom());
        }
    }

    q->setGeometry(newRect);
}

void GAntFramelessWidgetPrivate::updateMouseStatus(const QPoint& pos)
{
    Q_Q(GAntFramelessWidget);

    m_ePos = MousePos::No;

    if (!m_bIsFixedWidth) {
        if (m_rectLeftBorder.contains(pos)) {
            m_ePos |= MousePos::Left;
        } else if (m_rectRightBorder.contains(pos)) {
            m_ePos |= MousePos::Right;
        }
    }

    if (!m_bIsFixedHeight) {
        if (m_rectTopBorder.contains(pos)) {
            m_ePos |= MousePos::Top;
        } else if (m_rectBottomBorder.contains(pos)) {
            m_ePos |= MousePos::Bottom;
        }
    }
}

void GAntFramelessWidgetPrivate::moveWidget(const QPoint& pos)
{
    Q_Q(GAntFramelessWidget);

    q->move(pos - m_dragPos);
}

void GAntFramelessWidgetPrivate::handleMouseDblClick(QMouseEvent* event)
{
    Q_Q(GAntFramelessWidget);

    if (m_rectTitle.contains(event->pos())) {
        q->isMaximized() ? q->showNormal() : q->showMaximized();
    }
}

void GAntFramelessWidgetPrivate::handleMouseMove(QMouseEvent* event)
{
    Q_Q(GAntFramelessWidget);

    auto globalPos = event->globalPosition().toPoint();
    auto rPos = q->mapFromGlobal(globalPos);
    if (m_bIsDrag) {
        if (m_bIsPressTitle && m_bIsMoveable) {
            if (q->isMaximized()) {
                auto normalGeo = q->normalGeometry();
                q->showNormal();

                QPoint centerPos{ normalGeo.width() / 2 , rPos.y() - DragableWidth };
                rPos -= centerPos;
                q->move(rPos);
                m_dragPos = std::move(centerPos);

            } else {
                moveWidget(event->globalPosition().toPoint());
            }

        } else {
            resizeWidget(event->globalPosition().toPoint());
        }

    } else {
        updateMouseStatus(rPos);
        updateMouseArrow();
    }
}

void GAntFramelessWidgetPrivate::setWidthHelper(int w)
{
    Q_Q(GAntFramelessWidget);

    m_bIsFixedWidth = (m_bIsFixedWidth && w == q->width());
}

void GAntFramelessWidgetPrivate::setHeightHelper(int h)
{
    Q_Q(GAntFramelessWidget);

    m_bIsFixedHeight = (m_bIsFixedHeight && h == q->height());
}

void GAntFramelessWidgetPrivate::init()
{
    Q_Q(GAntFramelessWidget);

    q->setAttribute(Qt::WA_TranslucentBackground);
    //q->setAttribute(Qt::WA_DeleteOnClose);
    q->setWindowFlags(q->windowFlags() | Qt::FramelessWindowHint);

    m_pTitleBar = new QWidget(q);
    m_pTitleBar->setFixedHeight(TitleHeight);
    m_pTitleBar->setStyleSheet("border-top-left-radius: 4px; border-top-right-radius: 4px;background-color: white;");
    
    QIcon closeIcon;
    closeIcon.addPixmap(QPixmap{ ":/image/image/common/close.png" }, QIcon::Normal);
    closeIcon.addPixmap(QPixmap{ ":/image/image/common/close.png" }, QIcon::Active);
    auto closeBtn = new QPushButton(m_pTitleBar);
    closeBtn->setIcon(closeIcon);
    QObject::connect(closeBtn, &QPushButton::clicked, q, &QWidget::close);

    m_pTitleLabel = new QLabel("GAntFramelessWidget", m_pTitleBar);

    auto hLayout = new QHBoxLayout(m_pTitleBar);
    hLayout->addWidget(m_pTitleLabel);
    hLayout->addStretch(1);
    hLayout->addWidget(closeBtn);

    m_pVLayout = new QVBoxLayout(q);
    m_pVLayout->addWidget(m_pTitleBar);
    m_pVLayout->setContentsMargins(ShadowWidth, ShadowWidth, ShadowWidth, ShadowWidth);
    m_pVLayout->setSpacing(0);

    q->setMouseTracking(true);
    qApp->installEventFilter(q);
}


GAntFramelessWidget::GAntFramelessWidget(QWidget* parent)
    : QWidget(parent)
    , d_ptr(new GAntFramelessWidgetPrivate(this))
{
    Q_D(GAntFramelessWidget);

    d->init();
}

GAntFramelessWidget::~GAntFramelessWidget()
{
    qApp->removeEventFilter(this);
}

QWidget* GAntFramelessWidget::titleBar() const
{
    Q_D(const GAntFramelessWidget);

    return d->m_pTitleBar;
}

void GAntFramelessWidget::setTitleBar(QWidget* titleBar)
{
    Q_D(GAntFramelessWidget);

    Q_ASSERT(titleBar);

    d->m_pVLayout->takeAt(0);
    d->m_pVLayout->insertWidget(0, titleBar);
    d->m_pTitleBar = titleBar;
}

QWidget* GAntFramelessWidget::contentWidget() const
{
    Q_D(const GAntFramelessWidget);

    return d->m_pVLayout->itemAt(1)->widget();
}

void GAntFramelessWidget::setContentWidget(QWidget* content)
{
    Q_D(GAntFramelessWidget);

    Q_ASSERT(content);

    d->m_pVLayout->insertWidget(1, content);
}

void GAntFramelessWidget::setMaximumWidth(int maxw)
{
    Q_D(GAntFramelessWidget);
    d->setWidthHelper(maxw);

    QWidget::setMaximumWidth(maxw);
}

void GAntFramelessWidget::setMaximumHeight(int maxh)
{
    Q_D(GAntFramelessWidget);
    d->setWidthHelper(maxh);

    QWidget::setMaximumHeight(maxh);
}

void GAntFramelessWidget::setMinimumWidth(int minw)
{
    Q_D(GAntFramelessWidget);
    d->setHeightHelper(minw);

    QWidget::setMinimumWidth(minw);
}

void GAntFramelessWidget::setMinimumHeight(int minh)
{
    Q_D(GAntFramelessWidget);
    d->setHeightHelper(minh);

    QWidget::setMinimumHeight(minh);
}

void GAntFramelessWidget::setMaximumSize(int maxw, int maxh)
{
    Q_D(GAntFramelessWidget);

    d->setWidthHelper(maxw);
    d->setHeightHelper(maxh);

    QWidget::setMaximumSize(maxw, maxh);
}

void GAntFramelessWidget::setMinimumSize(int minw, int minh)
{
    Q_D(GAntFramelessWidget);

    d->setWidthHelper(minw);
    d->setHeightHelper(minh);

    QWidget::setMinimumSize(minw, minh);
}

void GAntFramelessWidget::setFixedWidth(int width)
{
    Q_D(GAntFramelessWidget);

    d->m_bIsFixedWidth = true;
    QWidget::setFixedWidth(width);
}

void GAntFramelessWidget::setFixedHeight(int height)
{
    Q_D(GAntFramelessWidget);

    d->m_bIsFixedHeight = true;
    QWidget::setFixedWidth(height);
}

void GAntFramelessWidget::setFixedSize(const QSize& size)
{
    setFixedSize(size.width(), size.height());
}

void GAntFramelessWidget::setFixedSize(int width, int height)
{
    Q_D(GAntFramelessWidget);

    d->m_bIsFixedWidth = true;
    d->m_bIsFixedHeight = true;
    QWidget::setFixedSize(width, height);
}

void GAntFramelessWidget::setMoveable(bool moveable)
{
    Q_D(GAntFramelessWidget);

    d->m_bIsMoveable = moveable;
}

bool GAntFramelessWidget::isMoveable() const
{
    Q_D(const GAntFramelessWidget);

    return d->m_bIsMoveable;
}

void GAntFramelessWidget::paintEvent(QPaintEvent* event)
{
    QWidget::paintEvent(event);

    Q_D(GAntFramelessWidget);

    QPainter painter{this};

    QColor color{0, 0, 0, 50};
    for (int i = 0; i < ShadowWidth; i++) {
        QPainterPath path;
        path.setFillRule(Qt::WindingFill);
        path.addRoundedRect(ShadowWidth - i, ShadowWidth - i, this->width() - (ShadowWidth - i) * 2, this->height() - (ShadowWidth - i) * 2,
                            BorderWidth, BorderWidth);

        color.setAlpha(125 - qSqrt(i) * 50);
        painter.setPen(color);
        painter.drawPath(path);
    }
}

void GAntFramelessWidget::mousePressEvent(QMouseEvent* event)
{
    Q_D(GAntFramelessWidget);

    if (event->type() == QEvent::MouseButtonDblClick) {
        d->handleMouseDblClick(event);
    }

    d->m_bIsDrag = true;
    d->m_dragPos = event->globalPosition().toPoint() - this->frameGeometry().topLeft();
    d->m_bIsPressTitle = d->m_rectTitle.contains(event->position().toPoint());

    QWidget::mousePressEvent(event);
}

void GAntFramelessWidget::mouseMoveEvent(QMouseEvent* event)
{
    Q_D(GAntFramelessWidget);

    QWidget::mouseMoveEvent(event);
}

void GAntFramelessWidget::mouseReleaseEvent(QMouseEvent* event)
{
    Q_D(GAntFramelessWidget);

    d->m_bIsDrag = false;
    d->m_bIsPressTitle = false;

    QWidget::mouseReleaseEvent(event);
}

void GAntFramelessWidget::resizeEvent(QResizeEvent* event)
{
    Q_D(GAntFramelessWidget);

    d->updateRects(this->rect());

    QWidget::resizeEvent(event);
}

bool GAntFramelessWidget::eventFilter(QObject* watched, QEvent* event)
{
    Q_D(GAntFramelessWidget);

    if (event->type() == QEvent::MouseMove) {
        auto me = static_cast<QMouseEvent*>(event);
        d->handleMouseMove(me);
    }

    return QWidget::eventFilter(watched, event);
}